比赛昨天下午开始,结果三个人昨天浪了一天,只能拖到今天看了看题目

FLOW


拿到题目,看了看题目The time is precious。时间是宝贵的… 可能是出题人觉得题目太简单了???使用IDA反编译看了一下,发现是一堆的函数,但是反编译中有很多是和Python相关的,通过strings 工具查看文件。发现py2exe,考虑可能是Python开发的exe,通过pyinstaller打包后的exe程序。

string.png

exe转换为pyc


我们可以通过unpy2exe.py将文件转换为.pyc然后进一步通过rePy2exe.py将.pyc文件转换为py文件,经过处理后如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# 2018.12.01 18:18:26 PST
#Embedded file name: CaR.py
import sys, os, hashlib, time, base64
import random
import itertools
from flag import flag

class car_class_l1:

def __init__(self, public_car = None, car_lenth = 16):
self.car_lenth = car_lenth
self.public_car = public_car
car_l1 = hashlib.md5(self.public_car.encode('utf-8')).hexdigest()
self.car_a = hashlib.md5(car_l1[0:16].encode('utf-8')).hexdigest()
self.car_b = hashlib.md5(car_l1[16:32].encode('utf-8')).hexdigest()
self.car_c = ''

def encode(self, string):
self.car_c = hashlib.md5(str(1234).encode('utf-8')).hexdigest()[32 - self.car_lenth:32]
string = '0000000000' + hashlib.md5((string + self.car_b).encode('utf-8')).hexdigest()[0:16] #+ string
print string,len(string)
self.result = ''
self.decrypt(string)
return str(self.car_c + base64.b64encode(self.result))
# st = str(self.car_c + base64.b64encgode(self.result))
def decrypt(self, string):
string_lenth = len(string)
result = ''
char_set = list(range(256))
char_rand_list = []
crypt_string1 = self.car_a + hashlib.md5((self.car_a + self.car_c).encode('utf-8')).hexdigest()
crypt_string1_len = len(crypt_string1)
for count_i in range(255):
char_rand_list.append(ord(crypt_string1[count_i % crypt_string1_len]))
#print char_rand_list
for count_i in range(255):
count_j = 0
count_j = (count_j + char_set[count_i] + char_rand_list[count_i]) % 256
de_result = char_set[count_i]
char_set[count_i] = char_set[count_j]
char_set[count_j] = de_result
#print char_set
for count_i in range(string_lenth):
count_k = 0
count_j = 0
count_k = (count_k + 1) % 256
count_j = (count_j + char_set[count_k]) % 256
de_result = char_set[count_k]
char_set[count_k] = char_set[count_j]
char_set[count_j] = de_result
self.result += chr(ord(string[count_i]) ^ char_set[(char_set[count_k] + char_set[count_j]) % 256])


def finallu_chng(finallu):
W = 4
perm = range(W)
random.shuffle(perm)
while len(finallu) % (2 * W):
finallu += '.'
print finallu
for count_i in xrange(100):
finallu = finallu[1:] + finallu[:1]
finallu = finallu[0::2] + finallu[1::2]
finallu = finallu[1:] + finallu[:1]
res = ''
for count_j in xrange(0, len(finallu), W):
for lchar_setl in xrange(W):
res += finallu[count_j:count_j + W][perm[lchar_setl]]
#print finallu[count_j:count_j + W][perm[lchar_setl]]

finallu1 = res
return finallu1


if __name__ == '__main__':
rc = car_class_l1('sdfgowormznsjx9ooxxx')
string = flag
string = finallu_chng(string)
st = rc.encode(string)
print "The result is",st
# +++ okay decompyling CaR.py.pyc
# # decompiled 1 files: 1 okay, 0 failed, 0 verify failed
# # 2018.12.01 18:18:26 PST

算法逆向分析


在给出FLAG的字符串后,先经过finallu_chng()函数处理后,然后经过encode()得到最后的输出结果。连接到NC后得到字符串0036dbd8313ed055NJD5H1Ufzl75UfLf1JgrfxKNwNca3fcyERbivXzTt4x/hEPt+P7s3BiGfihF0N8Sm5woY4Jqjsn7qqiwU+y1/uN/,从源代码可知,我们得到的是st的值。我们需要的是FLAG的值。因此我们要从encode()函数依次向后推得到FLAG。

0x01 encode()函数分析

encode函数中,我们最后得到是base64加密的结果,self.car_c的长度是固定的,所以通过字符串st的值我们可以得到经过b64加密的字符串。

1
2
3
4
5
6
7
def encode(self, string):
self.car_c = hashlib.md5(str(1234).encode('utf-8')).hexdigest()[32 - self.car_lenth:32]
string = '0000000000' + hashlib.md5((string + self.car_b).encode('utf-8')).hexdigest()[0:16] #+ string
print string,len(string)
self.result = ''
self.decrypt(string)
return str(self.car_c + base64.b64encode(self.result))

将st的b64加密部分解密。

1
2
3
4
5
6
import base64
string_1 = "0036dbd8313ed055NJD5H1Ufzl75UfLf1JgrfxKNwNca3fcyERbivXzTt4x/hEPt+P7s3BiGfihF0N8Sm5woY4Jqjsn7qqiwU+y1/uN/"
print string_1[0:16]
car_c = string_1[0:16]
bs64_res = string_1[16:]#b64加密部分
de_res = base64.b64decode(bs64_res)

解密后我们可以得到的是self.result,在encode()函数中,当我们得到self.resul后,接下来分析Decrypt()函数。

0x02 decrypt函数

在decrypt函数中,char_set = list(range(256))生成了256个字符的一个列表,之后经过一系列操作,列表中的顺序有所改变。但是最后的顺序是固定的。然后在函数的最后通过异或操作与string输入字符串进行异或。再异或依次便可以解密得到真正的string,再去掉一部分头部即可得到经过finallu_chng()加密后的字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
char_set = [202, 158, 160, 206, 1, 205, 163, 166, 215, 164, 64, 169, 114, 62, 67, 12, 118, 73, 115, 225, 177, 226, 180, 72, 181, 228, 82, 231, 233, 25, 185, 236, 186, 28, 85, 134, 89, 239, 246, 194, 244, 195, 97, 95, 43, 245, 252, 0, 203, 148, 207, 210, 104, 9, 3, 5, 8, 4, 105, 156, 60, 159, 221, 13, 10, 222, 121, 19, 167, 165, 227, 230, 31, 18, 128, 22, 178, 29, 131, 76, 125, 137, 123, 35, 241, 240, 81, 38, 39, 86, 34, 248, 251, 44, 41, 190, 139, 96, 48, 198, 2, 52, 50, 47, 46, 6, 14, 61, 107, 152, 106, 111, 11, 212, 59, 122, 168, 173, 66, 174, 63, 23, 26, 70, 119, 68, 71, 77, 74, 79, 136, 133, 84, 229, 33, 32, 36, 45, 145, 142, 242, 90, 40, 140, 189, 199, 243, 99, 197, 93, 201, 102, 154, 254, 98, 103, 110, 108, 155, 153, 57, 211, 213, 54, 217, 170, 56, 116, 214, 220, 78, 223, 124, 216, 171, 175, 75, 16, 30, 234, 232, 237, 132, 238, 127, 87, 130, 83, 183, 27, 53, 94, 191, 143, 249, 146, 193, 147, 49, 200, 208, 250, 101, 149, 100, 157, 51, 204, 253, 109, 58, 113, 161, 117, 21, 65, 218, 209, 162, 15, 224, 172, 219, 7, 112, 69, 176, 20, 126, 129, 120, 184, 17, 187, 179, 24, 188, 80, 235, 192, 88, 182, 91, 150, 138, 144, 135, 37, 141, 151, 92, 42, 247, 196, 55, 255]

def step1_decry(string_1):
result = ""
for count_i in range(len(string_1)):
count_k = 0
count_j = 0
count_k = (count_k + 1) % 256
count_j = (count_j + char_set[count_k]) % 256
de_result = char_set[count_k]
char_set[count_k] = char_set[count_j]
char_set[count_j] = de_result
result += chr(ord(string_1[count_i]) ^ char_set[(char_set[count_k] + char_set[count_j]) % 256])
print result
return result
0x03 finallu_chng函数

函数中首先生成一个列表[0,1,2,3]然后通过random.shuffle()函数打乱列表的顺序。while循环使得finallu的长度是8的倍数,不足的情况下补充 ‘.’,然后将如下操作循环100次。

  • 首字母后移到字符串末尾
  • 取字符串偶数部分的字符,字符串奇数部分的字符,构成新的字符串
  • 首字母移动到字符串末尾
  • 将新的字符串按照四个一组,按照新生成的列表perm里的顺序重新排列四个字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def finallu_chng(finallu):
W = 4
perm = range(W)
random.shuffle(perm)
while len(finallu) % (2 * W):
finallu += '.'
print finallu
for count_i in xrange(100):
finallu = finallu[1:] + finallu[:1]
finallu = finallu[0::2] + finallu[1::2]
finallu = finallu[1:] + finallu[:1]
res = ''
for count_j in xrange(0, len(finallu), W):
for lchar_setl in xrange(W):
res += finallu[count_j:count_j + W][perm[lchar_setl]]
#print finallu[count_j:count_j + W][perm[lchar_setl]]

finallu1 = res
return finallu1

得到算法流程后,逆向写出解题脚本,由于我们不知道正确的顺序,由于random.shuffle()每次生成的顺序不固定,所以我们可以通过多次random.shuffle来生成列表,爆破得到最后的flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import random
def finallu_chng(finallu):
W = 4
perm = range(W)
#random.shuffle(perm)
perm = [3,2,0,1]
while len(finallu) % (2 * W):
finallu += '.'
#print finallu
for count_i in xrange(100):
finallu = finallu[1:] + finallu[:1]
finallu = finallu[0::2] + finallu[1::2]
finallu = finallu[1:] + finallu[:1]
res = ''
for count_j in xrange(0, len(finallu), W):
for lchar_setl in xrange(W):
res += finallu[count_j:count_j + W][perm[lchar_setl]]
finallu = res
return finallu
def decrypt(string1):
W = 4
flag_res = string1
perm = [0,1,2,3]
random.shuffle(perm)
for count_i in xrange(100):
res = ""
for i in xrange(0,len(string1),W):
for k in xrange(4):
res += flag_res[i:i+W][perm.index(k)]#关键处,index使用的是正确的顺序
flag_res = res
flag_res = flag_res[-1:]+flag_res[:-1]
#print flag_res
res = ""
for i in range(0,len(flag_res)/2):
res = res + flag_res[i]+flag_res[i+len(flag_res)/2]
flag_res = res
flag_res = flag_res[-1:]+flag_res[:-1]
return flag_res

if __name__ == "__main__":
result = tep1_decry(de_res)
result = result[26:]
for i in range(0,100):
flag = decrypt(result)
if "flag" in flag:
print flag

flag{9789e2e95ec53daea99d54b3ca9c357d}

总结


exe 到python的逆向,可以使用工具rePy2exe.py,github链接https://github.com/4w4k3/rePy2exe,也可以参考http://blog.nsfocus.net/wp-content/uploads/2015/09/reverse_400.pdf,使用工具 PyInstaller Extractor 通过 python pyinstxtractor.py Revesre03.exe来解题,也可以通过uncompyle https://github.com/wibiti/uncompyle2 uncompyle,pycdc 来解题。